/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
package org.unidata.mdm.dq.core.dto;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.unidata.mdm.dq.core.service.DataQualityService;
import org.unidata.mdm.dq.core.type.io.DataQualityError;
import org.unidata.mdm.dq.core.type.io.DataQualityOutput;
import org.unidata.mdm.dq.core.type.io.DataQualitySpot;
import org.unidata.mdm.dq.core.type.io.DataQualityState;
import org.unidata.mdm.dq.core.type.model.instance.MappingSetElement;
import org.unidata.mdm.dq.core.type.model.instance.QualityRuleElement;

/**
 * @author Mikhail Mikhailov on Feb 27, 2021<br>
 * Result of {@link DataQualityService#apply(org.unidata.mdm.dq.core.context.DataQualityContext)} execution.
 * Contains <br>
 * <ul>
 * <li>rule execution states collections, grouped by rule rule set name.</li>
 * <li>enriched records</li>
 * </ul>
 */
public class DataQualityResult {
    /**
     * Collected state, keyed by rule rule set name.
     */
    private final Map<MappingSetElement, List<RuleExecutionResult>> results = new HashMap<>();
    /**
     * The payload to save.
     */
    private Object payload;
    /**
     * The input.
     */
    private DataQualityOutput output;
    /**
     * The overall result's validity state - is true,
     * if all rules completed with true.
     */
    private boolean valid = true;
    /**
     * Is true, if the rule produced some successful enrichments.
     */
    private boolean enriched = false;
    /**
     * Constructor.
     */
    public DataQualityResult() {
        super();
    }
    /**
     * Adds a state to result.
     * @param state the state to add
     */
    public void add(RuleExecutionResult state) {
        if (Objects.nonNull(state)) {

            results
                .computeIfAbsent(state.getSet(), k -> new ArrayList<>())
                .add(state);

            if (this.valid && !state.isValid()) {
                this.valid = false;
            }

            if (!this.enriched && state.isEnriched()) {
                this.enriched = true;
            }
        }
    }
    /**
     * Adds a collection of states to result.
     * @param states the states to add
     */
    public void addAll(Collection<RuleExecutionResult> states) {
        if (CollectionUtils.isNotEmpty(states)) {
            for (RuleExecutionResult e : states) {
                add(e);
            }
        }
    }
    /**
     * Gets rule execution states by rule rule set name.
     * @param setName the set name
     * @return collection
     */
    public List<RuleExecutionResult> getResults(String setName) {

        for (Entry<MappingSetElement, List<RuleExecutionResult>> entry : results.entrySet()) {
            if (StringUtils.equals(setName, entry.getKey().getName())) {
                return entry.getValue();
            }
        }

        return Collections.emptyList();
    }
    /**
     * Gets all rule execution states, keyed by rule rule set names.
     * @return all rule execution states, keyed by rule rule set names
     */
    public Map<MappingSetElement, List<RuleExecutionResult>> getResults() {
        return results;
    }
    /**
     * @return the payload
     */
    @SuppressWarnings("unchecked")
    public<T> T getPayload() {
        return (T) payload;
    }
    /**
     * @param payload the payload to set
     */
    public void setPayload(Object payload) {
        this.payload = payload;
    }
    /**
     * @return the output
     */
    public DataQualityOutput getOutput() {
        return output;
    }
    /**
     * @param output the output to set
     */
    public void setOutput(DataQualityOutput output) {
        this.output = output;
    }
    /**
     * @return the valid
     */
    public boolean isValid() {
        return valid;
    }
    /**
     * @return the enriched
     */
    public boolean isEnriched() {
        return enriched;
    }
    /**
     * @author Mikhail Mikhailov on Mar 10, 2021
     * Rule execution result.
     */
    public static class RuleExecutionResult {
        /**
         * The name of the rule.
         */
        private final QualityRuleElement rule;
        /**
         * The name of the rule rule set.
         */
        private final MappingSetElement set;
        /**
         * Errors, collected for the rule.
         */
        private List<DataQualityError> errors;
        /**
         * Rule call state. Only present in case of validation failures.
         */
        private List<DataQualityState> states;
        /**
         * Input failure/problem spots.
         */
        private List<DataQualitySpot> spots;
        /**
         * Skip indicator.
         */
        private boolean skipped;
        /**
         * The overall rule's validity state - is true,
         * if all cycles completed with true.
         */
        private boolean valid;
        /**
         * Is true, if the rule produced some successful enrichments.
         */
        private boolean enriched;
        /**
         * Constructor.
         * @param r the name
         */
        public RuleExecutionResult(MappingSetElement s, QualityRuleElement r) {
            super();

            Objects.requireNonNull(r, "Rule rule must not be null.");
            Objects.requireNonNull(s, "Mapping set must not be null.");

            this.rule = r;
            this.set = s;
        }
        /**
         * Constructor.
         * @param ruleName the name
         */
        public RuleExecutionResult(MappingSetElement s, QualityRuleElement r, boolean skip) {
            this(s, r);
            this.skipped = skip;
        }
        /**
         * @return the ruleName
         */
        public QualityRuleElement getRule() {
            return rule;
        }
        /**
         * @return the setName
         */
        public MappingSetElement getSet() {
            return set;
        }
        /**
         * Adds an error to result.
         * @param error the error to add
         */
        public void add(DataQualityError error) {
            if (Objects.nonNull(error)) {
                if (Objects.isNull(errors)) {
                    errors = new ArrayList<>(4);
                }
                errors.add(error);
            }
        }
        /**
         * Adds a collection of errors to result.
         * @param errors the errors to add
         */
        public void addErrors(Collection<DataQualityError> errors) {
            if (CollectionUtils.isNotEmpty(errors)) {
                for (DataQualityError e : errors) {
                    add(e);
                }
            }
        }
        /**
         * @return the errors
         */
        public List<DataQualityError> getErrors() {
            return Objects.isNull(errors) ? Collections.emptyList() : errors;
        }
        /**
         * Returns true, if this rule execution has any errors set.
         * @return true, if this rule execution has any errors set
         */
        public boolean hasErrors() {
            return CollectionUtils.isNotEmpty(errors);
        }
        /**
         * Adds a call state to result.
         * @param state the state to add
         */
        public void add(DataQualityState state) {
            if (Objects.nonNull(state)) {
                if (Objects.isNull(states)) {
                    states = new ArrayList<>(4);
                }
                states.add(state);
            }
        }
        /**
         * Adds a collection of errors to result.
         * @param errors the errors to add
         */
        public void addStates(Collection<DataQualityState> errors) {
            if (CollectionUtils.isNotEmpty(errors)) {
                for (DataQualityState e : errors) {
                    add(e);
                }
            }
        }
        /**
         * Gets function port call states.
         * @return the states
         */
        public List<DataQualityState> getStates() {
            return Objects.isNull(states) ? Collections.emptyList() : states;
        }
        /**
         * Returns true, if this rule execution has any call states set.
         * @return true, if this rule execution has any call states set
         */
        public boolean hasStates() {
            return CollectionUtils.isNotEmpty(states);
        }
        /**
         * Adds a failure/problem spot to result.
         * @param failure the failure to add
         */
        public void add(DataQualitySpot failure) {
            if (Objects.nonNull(failure)) {
                if (Objects.isNull(spots)) {
                    spots = new ArrayList<>(4);
                }
                spots.add(failure);
            }
        }
        /**
         * Adds a collection of failure/problem spots to result.
         * @param failures the failures to add
         */
        public void addSpots(Collection<DataQualitySpot> failures) {
            if (CollectionUtils.isNotEmpty(failures)) {
                for (DataQualitySpot e : failures) {
                    add(e);
                }
            }
        }
        /**
         * Gets failure/problem spots.
         * @return the failures
         */
        public List<DataQualitySpot> getSpots() {
            return Objects.isNull(spots) ? Collections.emptyList() : spots;
        }
        /**
         * Returns true, if this rule execution has any failures set.
         * @return true, if this rule execution has any failures set
         */
        public boolean hasSpots() {
            return CollectionUtils.isNotEmpty(spots);
        }
        /**
         * @return the skipped
         */
        public boolean isSkipped() {
            return skipped;
        }
        /**
         * Sets skip indicator to true.
         */
        public void skip() {
            skipped = true;
        }
        /**
         * @return the valid
         */
        public boolean isValid() {
            return valid;
        }
        /**
         * @param valid the valid to set
         */
        public void setValid(boolean valid) {
            this.valid = valid;
        }
        /**
         * @return the enriched
         */
        public boolean isEnriched() {
            return enriched;
        }
        /**
         * @param enriched the enriched to set
         */
        public void setEnriched(boolean enriched) {
            this.enriched = enriched;
        }
    }
}
